home *** CD-ROM | disk | FTP | other *** search
/ The Best of MacTutor - S…e Code for Volumes 1 to 5 / The Best of MacTutor - Source Code for Volume 1-5 (Wayzata Technology)(6031)(1990).bin / Source Code / #50 (Nov 89) / MIDIArp.Application / MIDIArp.Application⁄LICENSE / MIDIArp.c < prev    next >
C/C++ Source or Header  |  1989-06-29  |  29KB  |  1,152 lines

  1. /*
  2.     File:        MIDIArp.c   
  3.     Usage:        MIDI Manager Arpeggiator Program.
  4.     Purpose:    Apple MIDI Manager Demo.
  5.     Authors:        Fred Malouf, 
  6.                     Don Marsh,
  7.                     Don Veca.
  8.     Date:        April 1989
  9.     
  10. */
  11.  
  12. #include <stdIO.h>
  13. #include <stdlib.h>
  14. #include <Values.h>
  15. #include <Types.h>
  16. #include <Resources.h>
  17. #include <QuickDraw.h>
  18. #include <Fonts.h>
  19. #include <Events.h>
  20. #include <Controls.h>
  21. #include <Windows.h>
  22. #include <Menus.h>
  23. #include <TextEdit.h>
  24. #include <Dialogs.h>
  25. #include <Desk.h>
  26. #include <Scrap.h>
  27. #include <ToolUtils.h>
  28. #include <Memory.h>
  29. #include <SegLoad.h>
  30. #include <Files.h>
  31. #include <OSUtils.h>
  32. #include <OSEvents.h>
  33. #include <Traps.h>
  34. #include <Errors.h>
  35. #include <Sound.h>
  36. #include <MIDI.h>
  37.  
  38. #include "MIDIDefs.h"
  39. #include "MIDIArp.h"
  40.  
  41. #define updatePeriod    5    // For WaitNextEvent().
  42.  
  43. DialogPtr    GMainDialog;    // The Main Dialog Box Pointer.
  44. ArpParams    ArpGlobals;        // Global data for the arpeggiator.
  45. short        GCurSpeedID;    // Currently selected speed control.
  46. Boolean        GManualPatch;    // True if not launched by a PatchBay Config. File.
  47. Boolean        GDone = false;    // Global Exit Flag.
  48. char        GMIDIMgrVerStr[256]; // MIDI Manager Ver (Std Mac Ver # String)
  49.  
  50.  
  51. main() 
  52. {    
  53.     InitThings();    // Initialize Managers and some App globals.
  54.     ArpInit();        // Sign in to MIDI Mgr and add time/data ports.
  55.     StartDialog();    // Bring up main dialog box with default values.
  56.     RunDialog();    // Handle events.
  57.     ArpClose();        // Sign out from MIDI Mgr.
  58.     StopDialog();    // Close dialog box.
  59. }
  60.   
  61. // Initialize Managers and other generic stuff.
  62. void
  63. InitThings(void)
  64. {
  65.     
  66.     FlushEvents(everyEvent, 0);        // Empty any stray events.
  67.     InitGraf(&qd.thePort);            // Initialize quickdraw.
  68.     InitFonts();                    // Initialize Font Manager.
  69.     InitWindows();                    // Initialize Window Manager.
  70.     InitMenus();                    // Initialize Menu Manager.    
  71.     TEInit();                        // Initialize Text Edit.    
  72.     InitDialogs(NULL);                // Initialize the Dialog Manager.
  73.     InitCursor();                    // Turn cursor back to arrow.    
  74.     
  75.     {        // Set up a standard menu bar.
  76.         Handle MenuBar = GetNewMBar(menuBar);        // Read menus into menu bar.
  77.         SetMenuBar(MenuBar);                        // Install menus.
  78.         DisposHandle(MenuBar);
  79.         AddResMenu( GetMHandle(appleMenu), 'DRVR');    // Add DA names to Apple menu.
  80.         DrawMenuBar();
  81.     }
  82.     
  83.         // Seed the random number generator.
  84.     Seed();
  85.     
  86.         
  87. }
  88.  
  89. // Sign into the MIDI Manager.
  90. // Set up time, input, and output ports.
  91. // Initialize necessary arp globals.
  92. // Start our time base clock.
  93. void 
  94. ArpInit(void)
  95. {
  96.     MIDIPortParams    Init;    // MIDI Mgr Init data structure 
  97.     Handle            TheIconHndl;
  98.     OSErr            TheErr;
  99.     long            MIDIMgrVerNum;    // MIDI Manager Ver (Std Mac Ver #)
  100.     char            CStrBuf1[256];
  101.     
  102.     
  103.         // Make sure MIDIMgr is instaled and save version num.
  104.     MIDIMgrVerNum = SndDispVersion(midiToolNum);
  105.     if (MIDIMgrVerNum == 0)
  106.     {
  107.         ArpAlert("The MIDI Manager is not installed!  Aborting...");
  108.         ExitToShell();
  109.     }
  110.     else
  111.     {    
  112.         StdMacVerNumToStr(MIDIMgrVerNum, GMIDIMgrVerStr);
  113.         sprintf(CStrBuf1,"MIDI Manager Version %s", GMIDIMgrVerStr);
  114.         ArpAlert(CStrBuf1);
  115.     }
  116.     
  117.         // Sign in to the MIDI Manager.
  118.     TheIconHndl = GetResource('ICN#', arpIcon);
  119.     TheErr = MIDISignIn(arpClientID,
  120.                         refCon0, 
  121.                         TheIconHndl,
  122.                         "\pMIDIArp");
  123.     if (TheErr) 
  124.     {
  125.         ArpAlert("Trouble signing MIDIArp into MIDI Manager!  Aborting...");
  126.         ExitToShell();
  127.     }
  128.     
  129.         // Assume not a Patchbay configuration.
  130.     GManualPatch = true;    
  131.  
  132.         // Add time port.
  133.     Init.portID = timePortID;
  134.     Init.portType = midiPortTypeTime;
  135.     Init.timeBase = noTimeBaseRefNum;
  136.     Init.readHook = noReadHook;
  137.     Init.initClock.sync = midiInternalSync;
  138.     Init.initClock.curTime = zeroTime;
  139.     Init.initClock.format = midiFormatMSec;
  140.     Init.refCon = SetCurrentA5();
  141.     C2PStrCpy("TimeBase", Init.name);
  142.     TheErr = MIDIAddPort(arpClientID, timePortBuffSize, &(ArpGlobals.TimeRefNum), &Init);
  143.         // Has a PatchBay connection been resolved?
  144.     if (TheErr == midiVConnectMade) 
  145.     {
  146.         GManualPatch = false;
  147.     }
  148.     else if (TheErr == memFullErr)
  149.     {
  150.         ArpAlert("Not enough room in heap zone to add time port!  Aborting...");
  151.         MIDISignOut(arpClientID);    
  152.         ExitToShell();
  153.     }
  154.     
  155.         // Add an input port.
  156.     Init.portID = inputPortID;
  157.     Init.portType = midiPortTypeInput;
  158.     Init.timeBase = ArpGlobals.TimeRefNum;
  159.     Init.offsetTime = midiGetCurrent;
  160.     Init.readHook = (Ptr) ArpReader;
  161.     Init.refCon = SetCurrentA5();
  162.     C2PStrCpy("InputPort", Init.name);
  163.     TheErr = MIDIAddPort(arpClientID, inputPortBuffSize, &(ArpGlobals.InputRefNum), &Init);
  164.         // Has a PatchBay connection been resolved?
  165.     if (TheErr == midiVConnectMade) 
  166.     {
  167.         GManualPatch = false;
  168.     }
  169.     else if (TheErr == memFullErr)
  170.     {
  171.         ArpAlert("Not enough room in heap zone to add input port!  Aborting...");
  172.         MIDISignOut(arpClientID);    
  173.         ExitToShell();
  174.     }
  175.     
  176.         // Add an output port.
  177.     Init.portID = outputPortID;
  178.     Init.portType = midiPortTypeOutput;
  179.     Init.timeBase = ArpGlobals.TimeRefNum;
  180.     Init.offsetTime = midiGetCurrent;
  181.     Init.readHook = NULL;
  182.     Init.refCon = &ArpGlobals;
  183.     C2PStrCpy("OutputPort", Init.name);
  184.     TheErr = MIDIAddPort(arpClientID, outputPortBuffSize, &(ArpGlobals.OutputRefNum), &Init);
  185.         // Has a PatchBay connection been resolved?
  186.     if (TheErr == midiVConnectMade)     
  187.     {
  188.         GManualPatch = false;
  189.     }
  190.     else if (TheErr == memFullErr)
  191.     {
  192.         ArpAlert("Not enough room in heap zone to add output port!  Aborting...");
  193.         MIDISignOut(arpClientID);    
  194.         ExitToShell();    }
  195.     
  196.         // Not a PatchBay patch?
  197.     if (GManualPatch)
  198.     {
  199.             // Connect ports as they were when we last quit.
  200.         PatchPorts();
  201.     }
  202.     
  203.         // Init other globals.
  204.     ArpGlobals.Locked = false;
  205.     ArpGlobals.NumNotes = 0;
  206.     ArpGlobals.ArpPattern = patternUpDownID;
  207.     ArpGlobals.Tempo = speedMedium;
  208.     GCurSpeedID = speedMediumID;
  209.     //ArpGlobals.ArpTimeProcPtr = ArpTimeProc;
  210.     
  211.         // Start our Clock.
  212.     MIDIStartTime(ArpGlobals.TimeRefNum);        
  213.     
  214. }
  215.  
  216. // Display the dialog. Initialize the radio buttons and static text.
  217. void
  218. StartDialog(void)
  219. {
  220.     
  221.     GMainDialog = GetNewDialog(mainDialogID, NULL, (WindowPtr) -1);
  222.     SetPort(GMainDialog);
  223.     
  224.         // Set the radio control buttons to match the initialization  
  225.         // done in ArpInit().
  226.     ChangeState(GMainDialog, ArpGlobals.ArpPattern == patternUpID, patternUpID);
  227.     ChangeState(GMainDialog, ArpGlobals.ArpPattern == patternDownID, patternDownID);
  228.     ChangeState(GMainDialog, ArpGlobals.ArpPattern == patternUpDownID, patternUpDownID);
  229.     ChangeState(GMainDialog, ArpGlobals.ArpPattern == patternDownUpID, patternDownUpID);
  230.     ChangeState(GMainDialog, ArpGlobals.ArpPattern == patternRandomID, patternRandomID);
  231.     
  232.     ChangeState(GMainDialog, GCurSpeedID == speedVeryFastID, speedVeryFastID);
  233.     ChangeState(GMainDialog, GCurSpeedID == speedFastID, speedFastID);
  234.     ChangeState(GMainDialog, GCurSpeedID == speedMediumID, speedMediumID);
  235.     ChangeState(GMainDialog, GCurSpeedID == speedSlowID, speedSlowID);
  236.     ChangeState(GMainDialog, GCurSpeedID == speedVerySlowID, speedVerySlowID);
  237.     
  238.     ShowWindow(GMainDialog);
  239.     StdAdjustDLOGLocation(GMainDialog);
  240.  
  241. }
  242.  
  243. // Main Event Loop    
  244. void
  245. RunDialog(void)
  246. {
  247.     OSErr            TheErr = noErr;
  248.     short            ItemHit;
  249.     EventRecord        AnEvent;
  250.     WindowPtr        WhichWindow;
  251.     Rect            Boundry;
  252.     MIDIPortInfoHdl    PortInfoH;
  253.     GrafPtr            SavePort;
  254.  
  255.     
  256.     while (!GDone) {
  257.     
  258.             // Check to see if we have been connected
  259.             // to an external time base.
  260.         if ( MIDIWorldChanged(arpClientID) )
  261.         {
  262.             PortInfoH = MIDIGetPortInfo(arpClientID, timePortID);
  263.             if ( (**PortInfoH).timeBase.clientID != noClient )
  264.             {
  265.                 MIDISetSync(ArpGlobals.TimeRefNum, midiExternalSync);
  266.             }
  267.             else
  268.             {
  269.                 MIDISetSync(ArpGlobals.TimeRefNum, midiInternalSync);
  270.             }
  271.             DisposHandle((Handle) PortInfoH);
  272.         }
  273.             
  274.         if ( WaitNextEvent(everyEvent, &AnEvent, updatePeriod, NULL) ) 
  275.         {
  276.             
  277.             if ( (AnEvent.what == keyDown) && (AnEvent.modifiers & cmdKey) ) 
  278.             {
  279.                 AdjustMenus();
  280.                 DoMenuCommand( MenuKey(AnEvent.message & charCodeMask) );
  281.             }
  282.             
  283.             if ( IsDialogEvent(&AnEvent) ) 
  284.             {
  285.                 if (AnEvent.what == updateEvt)
  286.                 {
  287.                     GetPort(&SavePort);
  288.                     SetPort((GrafPtr) AnEvent.message);
  289.                     StdHiliteButton(GMainDialog, quitID);            // Hilite Quit Button.
  290.                     // TEIdle( ((DialogPeek)GMainDialog)->textH );    // Blink Cursor.
  291.                     SetPort(SavePort);
  292.                 }
  293.     
  294.                 if (AnEvent.what == keyDown 
  295.                     && ( (AnEvent.message & charCodeMask) == charEnterKey )) 
  296.                 {
  297.                     GDone = true;
  298.                 }
  299.                 else if ( DialogSelect(&AnEvent, &GMainDialog, &ItemHit) ) 
  300.                 {
  301.                     switch (ItemHit)
  302.                     {
  303.                         case quitID:
  304.                             GDone = true;
  305.                             break;
  306.                             
  307.                         case patternUpID:
  308.                         case patternDownID:
  309.                         case patternUpDownID:
  310.                         case patternDownUpID:
  311.                         case patternRandomID:
  312.                             SwitchRadio(GMainDialog, &(ArpGlobals.ArpPattern), ItemHit);
  313.                             break;
  314.                             
  315.                         case speedVeryFastID:
  316.                             ArpGlobals.Tempo = speedVeryFast;
  317.                             SwitchRadio(GMainDialog, &GCurSpeedID, ItemHit);
  318.                             break;
  319.                             
  320.                         case speedFastID:
  321.                             ArpGlobals.Tempo = speedFast;
  322.                             SwitchRadio(GMainDialog, &GCurSpeedID, ItemHit);
  323.                             break;
  324.                             
  325.                         case speedMediumID: 
  326.                             ArpGlobals.Tempo = speedMedium;
  327.                             SwitchRadio(GMainDialog, &GCurSpeedID, ItemHit);
  328.                             break;
  329.                             
  330.                         case speedSlowID:
  331.                             ArpGlobals.Tempo = speedSlow;
  332.                             SwitchRadio(GMainDialog, &GCurSpeedID, ItemHit);
  333.                             break;
  334.                             
  335.                         case speedVerySlowID:
  336.                             ArpGlobals.Tempo = speedVerySlow;
  337.                             SwitchRadio(GMainDialog, &GCurSpeedID, ItemHit);
  338.                             break;
  339.                                                         
  340.                         default:
  341.                             SysBeep(2);
  342.                             break;
  343.                             
  344.                     }    // End switch() 
  345.                     
  346.                 }        // End DialogSelect() 
  347.                 
  348.             }            // End IsDialogEvent() 
  349.             else {
  350.                 
  351.                 switch (AnEvent.what) 
  352.                 {
  353.                     case mouseDown:
  354.                         switch( FindWindow(AnEvent.where, &WhichWindow) ) 
  355.                         {
  356.                             case inMenuBar:
  357.                                 AdjustMenus();
  358.                                 DoMenuCommand( MenuSelect(AnEvent.where) );
  359.                                 break;
  360.                             case inSysWindow:
  361.                                 SystemClick(&AnEvent, WhichWindow);
  362.                                 break;
  363.                             case inContent:
  364.                                 if ( WhichWindow != FrontWindow() ) 
  365.                                 {
  366.                                     SelectWindow(WhichWindow);
  367.                                     AdjustMenus();
  368.                                 } 
  369.                                 break;
  370.                             case inGoAway:
  371.                                 if ( TrackGoAway(WhichWindow, AnEvent.where) ) 
  372.                                 {
  373.                                     GDone = true;
  374.                                 }
  375.                                 break;
  376.                             case inDrag:
  377.                                 SetRect(&Boundry, 4, 24, 
  378.                                         qd.screenBits.bounds.right - 4, 
  379.                                         qd.screenBits.bounds.bottom - 4
  380.                                 );
  381.                                 DragWindow(WhichWindow, AnEvent.where, &Boundry);
  382.                                 break;
  383.                             default:
  384.                                 break;
  385.                         }
  386.                         break;
  387.                         
  388.                     default:
  389.                         break;
  390.                 }                // End Switch 
  391.             }
  392.         }                        // End IF WaitNextEvent() 
  393.     }  
  394. }
  395.  
  396. // Save our patch config if necessary.
  397. // Sign out from the MIDI Manager.
  398. void
  399. ArpClose(void)
  400. {
  401.         // Not a PatchBay patch?
  402.     if (GManualPatch)
  403.     {
  404.             // Save current port connection configuration. 
  405.         SavePatch(timePortID, timePortResInfoID, "timePortInfo");
  406.         SavePatch(inputPortID, inputPortResInfoID, "inputPortInfo");
  407.         SavePatch(outputPortID, outputPortResInfoID, "outputPortInfo");
  408.     }
  409.     MIDISignOut(arpClientID);
  410. }
  411.  
  412. // Shut down the main dialog.
  413. void
  414. StopDialog(void)
  415. {
  416.     StdSaveDLOGLocation(GMainDialog, mainDialogID);
  417.     HideWindow(GMainDialog);
  418.     DisposDialog(GMainDialog);
  419. }
  420.  
  421. // The Read Hook Function.
  422. // Read all incomming MIDI data.
  423. // Set up first wake up when first 
  424. //        note of a series is received.
  425. pascal short 
  426. ArpReader(MIDIPacket *ThePacketPtr, long TheRefCon)
  427. {
  428.         // Set up our A5 world.
  429.     long    SysA5 = SetA5(TheRefCon);
  430.     short    RetVal = midiMorePacket, i, j;
  431.     
  432.     if(ArpGlobals.Locked) 
  433.     {
  434.         RetVal = midiKeepPacket;    
  435.             // Don't want to read packet now,
  436.             // I'll get it later.
  437.     }
  438.     else if ( ThePacketPtr->flags == stdPacketFlags &&     
  439.               ((ThePacketPtr->data[0] & statusMask) == keyOn ||
  440.                (ThePacketPtr->data[0] & statusMask) == keyOff)
  441.             )    
  442.     {
  443.         RetVal = midiMorePacket;
  444.             // Yes, we got this packet thank you,
  445.             // please send us more!
  446.         
  447.         /* NOTE ON? */
  448.         if ((ThePacketPtr->data[0] & statusMask) == keyOn && 
  449.              ThePacketPtr->data[2] != zeroVelo)
  450.         {
  451.                 // Determine where to insert new note.
  452.             for (i=0;i<ArpGlobals.NumNotes; i++)
  453.             {
  454.                 if (ThePacketPtr->data[1] <= ArpGlobals.NoteTbl[i].Note) 
  455.                 {
  456.                     break;    // i == index to insert note into.
  457.                 }
  458.             }
  459.             
  460.                 // Make room in the note table.
  461.             for (j=ArpGlobals.NumNotes; j>i; j--)
  462.             {
  463.                 ArpGlobals.NoteTbl[j].Channel = ArpGlobals.NoteTbl[j-1].Channel;
  464.                 ArpGlobals.NoteTbl[j].Note = ArpGlobals.NoteTbl[j-1].Note;
  465.                 ArpGlobals.NoteTbl[j].Velocity = ArpGlobals.NoteTbl[j-1].Velocity;
  466.             }
  467.                 // Insert new note.
  468.             ArpGlobals.NoteTbl[i].Channel = ThePacketPtr->data[0] & channelMask;
  469.             ArpGlobals.NoteTbl[i].Note = ThePacketPtr->data[1];
  470.             ArpGlobals.NoteTbl[i].Velocity = ThePacketPtr->data[2];
  471.             
  472.                 // Record number of valid notes in table.
  473.             if(ArpGlobals.NumNotes < noteTblSize) 
  474.             {
  475.                 ArpGlobals.NumNotes++;
  476.             }
  477.             
  478.             if(ArpGlobals.NumNotes == 1)    // First note down.
  479.             {
  480.                     // Start off arpeggiation.
  481.                 ArpGlobals.NextNoteOnTime = ThePacketPtr->tStamp;
  482.                 ArpTimeProc(ThePacketPtr->tStamp, TheRefCon);                    
  483.             }
  484.         }    
  485.         /* NOTE OFF? */
  486.         else if ((ThePacketPtr->data[0] & statusMask) == keyOff || 
  487.                  (ThePacketPtr->data[0] & statusMask) == keyOn)
  488.                  // If its keyOn, by now we know its velocity is 0.
  489.         {
  490.             // Find index in note table that contains same note on same Channel.
  491.             for (i=0;i< ArpGlobals.NumNotes; i++)
  492.             {
  493.                 if((ArpGlobals.NoteTbl[i].Channel == (ThePacketPtr->data[0] & channelMask)) &&
  494.                    (ArpGlobals.NoteTbl[i].Note == ThePacketPtr->data[1]))
  495.                 {
  496.                     break;    // i == index of note to delete.
  497.                 }
  498.             }
  499.             
  500.                 // Still a valid note?
  501.             if (i < ArpGlobals.NumNotes)
  502.             {
  503.                     // i is already whrere we want it.
  504.                 for (/*i=i*/; i<ArpGlobals.NumNotes-1; i++)
  505.                 {    
  506.                         // Delete (bury) the note.
  507.                     ArpGlobals.NoteTbl[i].Channel = ArpGlobals.NoteTbl[i+1].Channel;
  508.                     ArpGlobals.NoteTbl[i].Note = ArpGlobals.NoteTbl[i+1].Note;
  509.                     ArpGlobals.NoteTbl[i].Velocity = ArpGlobals.NoteTbl[i+1].Velocity;
  510.                 }
  511.                 ArpGlobals.NumNotes--;
  512.             }
  513.         }
  514.     }
  515.     
  516.         // Restore the systems A5 world.
  517.     SetA5(SysA5);
  518.     
  519.     return(RetVal);
  520. }
  521.  
  522.  
  523. // The Time Proc.
  524. // All data output is done from here.
  525. // Sets up next wakeUp based on ArpGlobals, 
  526. //        or cancels wakeUps if no notes are
  527. //        left in the note table.
  528. pascal void 
  529. ArpTimeProc(long /* 'TheCurTime' not used here */, long TheRefCon)
  530. {
  531.     long    SysA5 = SetA5(TheRefCon); // Set up our A5 world.
  532.     int        i;
  533.     MIDIPacket    TheMIDIPacket;
  534.     
  535.         // Warn read hook function not to mess with the data structures.
  536.     ArpGlobals.Locked = 1;
  537.  
  538.     if (ArpGlobals.NumNotes == 0) 
  539.     {
  540.             // Cancel wakeups until there is a new note to play.
  541.         MIDIWakeUp(ArpGlobals.TimeRefNum, zeroTime, zeroPeriod, noTimeProc);
  542.     }    
  543.     else
  544.     {    
  545.             // Set index of next note to be played.
  546.         BumpNoteTableIndex();
  547.         if (ArpGlobals.NoteTbl[ArpGlobals.NoteIndex] == ArpGlobals.LastNote)
  548.         {        // Avoid playing first note 
  549.                 // of an arpeggiation twice.
  550.             BumpNoteTableIndex();
  551.         }
  552.         ArpGlobals.LastNote = ArpGlobals.NoteTbl[ArpGlobals.NoteIndex];
  553.         
  554.         TheMIDIPacket.flags = stdPacketFlags;    
  555.         TheMIDIPacket.len = keyOnOffPacketSize;
  556.         
  557.             // Turn note on.
  558.         i = ArpGlobals.NoteIndex;
  559.         TheMIDIPacket.tStamp = ArpGlobals.NextNoteOnTime;
  560.         TheMIDIPacket.data[0] = ArpGlobals.NoteTbl[i].Channel | keyOn;
  561.         TheMIDIPacket.data[1] = ArpGlobals.NoteTbl[i].Note;
  562.         TheMIDIPacket.data[2] = ArpGlobals.NoteTbl[i].Velocity;
  563.         MIDIWritePacket(ArpGlobals.OutputRefNum, &TheMIDIPacket);
  564.             
  565.             // Turn note off (in the future).
  566.         TheMIDIPacket.tStamp += noteDuration;
  567.         TheMIDIPacket.data[0] -= 0x10;     // Quick way to convert to a note off.
  568.         MIDIWritePacket(ArpGlobals.OutputRefNum, &TheMIDIPacket);
  569.             
  570.             // Set time to play next note .
  571.         ArpGlobals.NextNoteOnTime += ArpGlobals.Tempo;        
  572.         
  573.             // Schedule next wakeup.
  574.             // The MIDIWakeUp will occur early 
  575.             // (NextNoteOnTime - ArpGlobals.Tempo/2),
  576.             // but the time stamp on the note that is being
  577.             // written is accurate (NextNoteOnTime).  
  578.             // This way, we eliminate the chance of the note
  579.             // being received late due to processing time.
  580.             // The basic rule of thumb is to always write early, 
  581.             // making sure that the time stamp (which is some 
  582.             // time in the future) is accurrate. 
  583.             // The MIDI Manager will take care of the details.
  584.         MIDIWakeUp(ArpGlobals.TimeRefNum, 
  585.                ArpGlobals.NextNoteOnTime - ArpGlobals.Tempo/2,
  586.                zeroPeriod, 
  587.                (ProcPtr) ArpTimeProc
  588.                );
  589.     }
  590.     
  591.         // OK for ArpReader to mess with the data structures now.
  592.     ArpGlobals.Locked = false;
  593.     
  594.         // Restore the systems A5 world.
  595.     SetA5(SysA5);
  596. }
  597.  
  598. // Bumps note index to next note in note table
  599. // based on current user preferences.
  600. void
  601. BumpNoteTableIndex(void)
  602. {
  603.     if (ArpGlobals.NumNotes == 1)
  604.     {
  605.         ArpGlobals.NoteIndex = 0;
  606.     }
  607.     else
  608.     {
  609.         switch(ArpGlobals.ArpPattern)
  610.         {
  611.             case patternUpID:
  612.                 if (ArpGlobals.NoteIndex >= ArpGlobals.NumNotes-1) 
  613.                 {    
  614.                     ArpGlobals.NoteIndex = 0;
  615.                 }
  616.                 else 
  617.                 {    
  618.                     ArpGlobals.NoteIndex++;
  619.                 }
  620.                 break;
  621.                 
  622.             case patternUpDownID:                    
  623.             case patternDownUpID:
  624.                 if (ArpGlobals.ArpDirection == goingUp)            
  625.                 {
  626.                         // First condition necessary
  627.                         // for the special case of 
  628.                         // one note    arpeggiation.
  629.                         // '>=' for both cases works,
  630.                         // but top note will be played TWICE.
  631.                     if (ArpGlobals.NoteIndex > ArpGlobals.NumNotes-1)    
  632.                     {
  633.                         ArpGlobals.NoteIndex = ArpGlobals.NumNotes-1;
  634.                         ArpGlobals.ArpDirection = goingDown;
  635.                     }
  636.                     else if (ArpGlobals.NoteIndex == ArpGlobals.NumNotes-1)
  637.                     {
  638.                         ArpGlobals.NoteIndex--;
  639.                         ArpGlobals.ArpDirection = goingDown;
  640.                     }
  641.                     else
  642.                     {
  643.                         ArpGlobals.NoteIndex++;
  644.                     }
  645.                 }
  646.                 else
  647.                 {                
  648.                         // First condition necessary
  649.                         // for the special case of 
  650.                         // one note    arpeggiation.
  651.                         // '<=' for both cases works,
  652.                         // but bottom note will be played TWICE.
  653.                     if (ArpGlobals.NoteIndex < 0)                    
  654.                     {
  655.                         ArpGlobals.NoteIndex = 0;
  656.                         ArpGlobals.ArpDirection = goingUp;
  657.                     }
  658.                     else if (ArpGlobals.NoteIndex == 0)
  659.                     {
  660.                         ArpGlobals.NoteIndex++;
  661.                         ArpGlobals.ArpDirection = goingUp;
  662.                     }
  663.                     else
  664.                     {
  665.                         ArpGlobals.NoteIndex--;
  666.                     }
  667.                 }
  668.                 break;
  669.                 
  670.             case patternDownID:
  671.                 if (ArpGlobals.NoteIndex <= 0) 
  672.                 {
  673.                     ArpGlobals.NoteIndex = ArpGlobals.NumNotes-1;
  674.                 }
  675.                 else
  676.                 {
  677.                     ArpGlobals.NoteIndex--;
  678.                 }
  679.                 break;
  680.                 
  681.             case patternRandomID:
  682.                 ArpGlobals.NoteIndex = Choose(ArpGlobals.NumNotes);
  683.                 break;
  684.         }
  685.     }
  686. }
  687.  
  688.  
  689. // Get previously saved port connections (port info records)
  690. // from application's 'port' resource.
  691. void
  692. PatchPorts(void)
  693. {
  694.     MIDIPortInfoHdl    PortInfoH;    // Handle to port info record.
  695.     MIDIPortInfoPtr    PortInfoP;    // Pointer to port info record.
  696.     short            i, TheErr;
  697.         
  698.         // SET UP TIME PORT CONNECTIONS.
  699.     PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, timePortResInfoID);
  700.     if (PortInfoH == NULL)
  701.     {
  702.         ReportResError("GetResource(portResType, timePortResInfoID)");
  703.     }
  704.     HLock((Handle) PortInfoH);
  705.     PortInfoP = *PortInfoH;
  706.     if (GetHandleSize((Handle) PortInfoH) != 0)
  707.     {
  708.             // Were we supposed to be sync'd to another client?
  709.         if (PortInfoP->timeBase.clientID != noClient)
  710.         {        
  711.                 // Yes, so make that client our time base.
  712.             TheErr = MIDIConnectTime(
  713.                         PortInfoP->timeBase.clientID, 
  714.                         PortInfoP->timeBase.portID,
  715.                         arpClientID, 
  716.                         timePortID 
  717.                         );
  718.                 // Is the client still signed in?
  719.             if (TheErr != midiVConnectErr) 
  720.             {    
  721.                     // Yes, so set our sync mode to external.
  722.                 MIDISetSync(ArpGlobals.TimeRefNum, midiExternalSync);
  723.             }
  724.             
  725.         }
  726.             // Were we somebody else's time base?
  727.         for (i=0; i<PortInfoP->numConnects; i++)
  728.         {
  729.             MIDIConnectTime(arpClientID, 
  730.                         timePortID, 
  731.                         PortInfoP->cList[i].clientID, 
  732.                         PortInfoP->cList[i].portID);
  733.         }
  734.     }
  735.     HUnlock((Handle) PortInfoH);
  736.     ReleaseResource((Handle) PortInfoH);
  737.     ReportResError("PatchPorts/ReleaseResource()");
  738.     
  739.         // SET UP INPUT PORT CONNECTIONS.
  740.     PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, inputPortResInfoID);
  741.     if (PortInfoH == NULL)
  742.     {
  743.         ReportResError("PatchPorts/GetResource()");
  744.     }
  745.     HLock((Handle) PortInfoH);
  746.     PortInfoP = *PortInfoH;
  747.     if (GetHandleSize((Handle) PortInfoH) != 0)
  748.     {
  749.             // Were we connected to anyone?
  750.         for (i=0; i<PortInfoP->numConnects; i++)
  751.         {
  752.             MIDIConnectData(arpClientID, 
  753.                         inputPortID, 
  754.                         PortInfoP->cList[i].clientID, 
  755.                         PortInfoP->cList[i].portID);
  756.         }
  757.     }
  758.     HUnlock((Handle) PortInfoH);
  759.     ReleaseResource((Handle) PortInfoH);
  760.     ReportResError("PatchPorts/GetResource()");
  761.     
  762.         // SET UP OUTPUT PORT CONNECTIONS.
  763.     PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, outputPortResInfoID);
  764.     if (PortInfoH == NULL)
  765.     {    
  766.         ReportResError("PatchPorts/GetResource()");
  767.     }
  768.     HLock((Handle) PortInfoH);
  769.     PortInfoP = *PortInfoH;
  770.     if (GetHandleSize((Handle) PortInfoH) != 0)
  771.     {
  772.             // Were we connected to anyone?
  773.         for (i=0; i<PortInfoP->numConnects; i++)
  774.         {
  775.             MIDIConnectData(arpClientID, 
  776.                         outputPortID, 
  777.                         PortInfoP->cList[i].clientID, 
  778.                         PortInfoP->cList[i].portID
  779.                         );
  780.         }
  781.     }
  782.     HUnlock((Handle) PortInfoH);
  783.     ReleaseResource((Handle) PortInfoH);
  784.     ReportResError("PatchPorts/ReleaseResource()");
  785.     
  786. }
  787.  
  788.  
  789. // Save current port connections (port info records)
  790. // to application's 'port' resource.
  791. void
  792. SavePatch(OSType PortID, short PortInfoResID, char *PortInfoResName)
  793. {
  794.     Handle            PortResH;    // Handle to ptch resource.
  795.     CursHandle        WatchCurs;    
  796.     
  797.     WatchCurs = GetCursor(watchCursor);
  798.     HLock((Handle) WatchCurs);
  799.     SetCursor(*WatchCurs);
  800.     HUnlock((Handle) WatchCurs);
  801.  
  802.     
  803.         // Remove existing port info resource.
  804.     PortResH = GetResource(portResType, PortInfoResID);
  805.     ReportResError("SavePatch/GetResource()");
  806.     RmveResource(PortResH);
  807.     ReportResError("SavePatch/RmveResource() (make sure disk is unlocked)");
  808.     DisposHandle(PortResH);
  809.     UpdateResFile(CurResFile());
  810.     ReportResError("SavePatch/UpdateResFile() (make sure disk is unlocked)");
  811.     
  812.         //    Get new configurateion.
  813.     PortResH = (Handle) MIDIGetPortInfo(arpClientID, PortID);
  814.     
  815.         //    Save new configurateion.
  816.     addresource(PortResH, portResType, PortInfoResID, PortInfoResName);
  817.     ReportResError("SavePatch/addresource()");
  818.     WriteResource(PortResH);
  819.     ReportResError("SavePatch/WriteResource() (make sure disk is unlocked)");
  820.     UpdateResFile(CurResFile());
  821.     ReportResError("SavePatch/UpdateResFile() (make sure disk is unlocked)");
  822.     ReleaseResource(PortResH);
  823.     ReportResError("SavePatch/ReleaseResource() (make sure disk is unlocked)");
  824.     
  825.     InitCursor();
  826. }
  827.  
  828. // Get String representation of MIDI Mgr Version Num.
  829. // See Mac Tech Note #189 for details.
  830. char *
  831. StdMacVerNumToStr(long VerNum, char *VerStr)
  832. {
  833.     char    *RetVal;
  834.     char    MajVer, MinVer, VerStage, VerRev, BugFixVer = 0;
  835.     
  836.     if (VerNum == 0)
  837.     {
  838.         RetVal = NULL;
  839.     }
  840.     else
  841.     {
  842.         MajVer         = (VerNum & 0xFF000000) >> 24;
  843.         MinVer         = (VerNum & 0x00FF0000) >> 16;
  844.         VerStage     = (VerNum & 0x0000FF00) >> 8;
  845.         VerRev         = (VerNum & 0x000000FF) >> 0;
  846.         BugFixVer    =  MinVer & 0x0F;
  847.         
  848.         switch (VerStage)
  849.         {
  850.             case 0x20:
  851.                 VerStage = 'd';
  852.                 break;
  853.             case 0x40:
  854.                 VerStage = 'a';
  855.                 break;
  856.             case 0x60:
  857.                 VerStage = 'b';
  858.                 break;
  859.             case 0x80:
  860.                 VerStage = '';
  861.                 break;
  862.             default:
  863.                 VerStage = '?';
  864.                 break;
  865.         }
  866.         
  867.         if (BugFixVer == 0)
  868.         {
  869.             sprintf(VerStr,"%X.%X%c%X", 
  870.                 MajVer, MinVer>>4, VerStage, VerRev);
  871.         }
  872.         else
  873.         {
  874.             sprintf(VerStr,"%X.%X.%X%c%X", 
  875.                 MajVer, MinVer >> 4, MinVer & 0x0F, VerStage, VerRev);
  876.         }
  877.         
  878.         RetVal = VerStr;
  879.     }
  880.         
  881.     return(RetVal);
  882. }
  883.             
  884.  
  885. // Exit gracefully on error.
  886. void 
  887. Terminate(void)
  888. {
  889.     MIDISignOut(arpClientID);    
  890.     StopDialog();
  891.     ExitToShell();
  892. }
  893.  
  894. void 
  895. DoMenuCommand(long MenuResult)
  896. {
  897.     short        MenuID;
  898.     short        MenuItem;
  899.     short        ItemHit;
  900.     Str255        DAName;
  901.     short        DARefNum;
  902.     WindowPtr    Window;
  903.  
  904.     Window = FrontWindow();
  905.     MenuID = HiWord(MenuResult);    // use macros for efficiency to... 
  906.     MenuItem = LoWord(MenuResult);    // get menu item number and menu number
  907.     switch ( MenuID ) {
  908.         case appleMenu:
  909.             switch ( MenuItem ) 
  910.             {
  911.                 case aboutItem:        // bring up alert for About.
  912.                     ItemHit = Alert(arpAboutAlertID, NULL);
  913.                     break;
  914.                     
  915.                 default:            // all non-About items in this menu are DAs
  916.                     GetItem(GetMHandle(appleMenu), MenuItem, DAName);
  917.                     DARefNum = OpenDeskAcc(DAName);
  918.                     break;
  919.             }
  920.             break;
  921.         case fileMenu:
  922.             switch ( MenuItem ) 
  923.             {
  924.                 case closeItem:
  925.                     if ( IsDAWindow(Window) )
  926.                     {
  927.                         CloseDeskAcc(((WindowPeek) Window)->windowKind);
  928.                     }
  929.                     else if ( IsAppWindow(Window) ) 
  930.                     {
  931.                         GDone = true;
  932.                     }
  933.                     break;
  934.                     
  935.                 case quitItem:
  936.                     GDone = true;
  937.                     break;
  938.                     
  939.                 default:
  940.                     break;
  941.             }
  942.             break;
  943.             
  944.         case editMenu:
  945.             SystemEdit(MenuItem-1);
  946.             break;
  947.             
  948.         default:
  949.             break;
  950.     }
  951.     HiliteMenu(0);        // Unhighlight what MenuSelect (or MenuKey) hilited 
  952.  
  953.  
  954. void 
  955. AdjustMenus(void)
  956. {
  957.     WindowPtr    Frontmost;
  958.     MenuHandle    menu;
  959.  
  960.     Frontmost = FrontWindow();
  961.     
  962.     menu = GetMHandle(editMenu);
  963.     
  964.     if ( IsDAWindow(Frontmost) ) 
  965.     {
  966.         EnableItem(menu, undoItem);
  967.         EnableItem(menu, cutItem);
  968.         EnableItem(menu, copyItem);
  969.         EnableItem(menu, pasteItem);
  970.         EnableItem(menu, clearItem);
  971.     } 
  972.     else if ( IsAppWindow(Frontmost) ) 
  973.     {
  974.         DisableItem(menu, undoItem);
  975.         DisableItem(menu, cutItem);
  976.         DisableItem(menu, copyItem);
  977.         DisableItem(menu, pasteItem);
  978.         DisableItem(menu, clearItem);
  979.     }
  980.  
  981.  
  982. //    Save the current location of the main dialog
  983. //    back into our dialog resource.
  984. void
  985. StdSaveDLOGLocation(DialogPtr TheDialogPtr, short TheDialogID)
  986. {
  987.     Handle    MyDLOGResHndl;
  988.     Rect    WhereItIs;
  989.     
  990.     MyDLOGResHndl = GetResource('DLOG', TheDialogID);
  991.     if (MyDLOGResHndl != NULL) {
  992.         HNoPurge(MyDLOGResHndl);
  993.         SetPort(TheDialogPtr);
  994.         WhereItIs = TheDialogPtr->portRect;
  995.         LocalToGlobal( (Point *) &(WhereItIs.top) );
  996.         LocalToGlobal( (Point *) &(WhereItIs.bottom) );
  997.         ( *( (DialogTHndl) MyDLOGResHndl ) )->boundsRect = WhereItIs;
  998.         ChangedResource(MyDLOGResHndl);
  999.         WriteResource(MyDLOGResHndl);
  1000.         HPurge(MyDLOGResHndl);
  1001.     }
  1002.     else 
  1003.     {
  1004.         ArpAlert("Error in StdSaveDLOGLocation():  MyDLOGResHndl = NULL!");
  1005.     }
  1006.     
  1007. }
  1008.  
  1009. //    If our main dialog was saved out of 
  1010. //    reach (on a bigger monitor), then move
  1011. //    it back within reach.
  1012. void
  1013. StdAdjustDLOGLocation(DialogPtr TheDialogPtr)
  1014. {
  1015.     Point    TestPoint;
  1016.     
  1017.     TestPoint.h = TheDialogPtr->portRect.left + 6;    // 6 is enough to grab
  1018.     TestPoint.v = TheDialogPtr->portRect.top + 6;    // 6 is enough to grab
  1019.     LocalToGlobal(&TestPoint);
  1020.     
  1021.     if ( !PtInRgn( TestPoint, GetGrayRgn() ) )     /* Reposition */
  1022.     {
  1023.         MoveWindow( (WindowPtr) TheDialogPtr, 
  1024.             qd.screenBits.bounds.left + 50,            
  1025.             qd.screenBits.bounds.top  + 50,
  1026.             false
  1027.         );
  1028.     }
  1029.     
  1030. }
  1031.  
  1032. // Change the state of a radio button, 
  1033. // and set their identifier vars accordingly.
  1034. void 
  1035. SwitchRadio(DialogPtr TheDialog, short *CurRadio, short NextRadio)
  1036. {
  1037.     ChangeState (TheDialog, 0, *CurRadio);
  1038.     ChangeState (TheDialog, 1, NextRadio);
  1039.     *CurRadio = NextRadio;
  1040. }
  1041.  
  1042. //     Set the state of a binary control, such as a radio button or
  1043. //    checkbox.
  1044. void 
  1045. ChangeState(DialogPtr TheDialog, short State, short ItemNo)
  1046. {
  1047.     short    ItemType;
  1048.     Handle    ItemHndl;
  1049.     Rect    ItemBox;
  1050.     
  1051.     GetDItem(TheDialog, ItemNo, &ItemType, &ItemHndl, &ItemBox);
  1052.     SetCtlValue ( (ControlHandle) ItemHndl, (State ? 1 : 0) );
  1053. }
  1054.  
  1055. //    Print out an Alert message to the user.
  1056. void
  1057. ArpAlert(char *TheMessage)
  1058. {
  1059.     paramtext(TheMessage, "","","");
  1060.     Alert(arpAlertBoxID, NULL);
  1061.     
  1062. }
  1063.  
  1064.  
  1065. //    Convert a C String (from Cstr) into a Pascal string, 
  1066. //    copying the result into a buffer (Pstr).
  1067. char *
  1068. C2PStrCpy(char *Cstr, Str255 Pstr)
  1069. {
  1070.     short i, Len = strlen(Cstr);
  1071.     
  1072.     for(i=Len; i>0; i--) {
  1073.         Pstr[i] = Cstr[i-1];
  1074.     }
  1075.     Pstr[i] = Len;
  1076.     
  1077.     return( (char *) Pstr );
  1078. }
  1079.  
  1080. Boolean 
  1081. IsAppWindow(WindowPtr window)
  1082. {
  1083.     if ( window == nil )
  1084.     {    return false;
  1085.     }
  1086.     else    /* application windows have non-negative windowKinds */
  1087.     {    return ((WindowPeek) window)->windowKind >= 0;
  1088.     }
  1089.  
  1090. Boolean 
  1091. IsDAWindow(WindowPtr Window)
  1092. {
  1093.     if ( Window == nil )
  1094.     {    return false;
  1095.     }
  1096.     else    /* DA windows have negative windowKinds */
  1097.     {    return ((WindowPeek) Window)->windowKind < 0;
  1098.     }
  1099.  
  1100. //    Boldface a button. 
  1101. void
  1102. StdHiliteButton(DialogPtr TheDialog, short TheItemID)
  1103. {
  1104.     short        ItemType;
  1105.     Handle        ItemHndl;
  1106.     Rect        ItemBox;
  1107.     
  1108.     GetDItem(TheDialog, TheItemID, &ItemType, &ItemHndl, &ItemBox);
  1109.     PenSize(3,3);
  1110.     InsetRect(&ItemBox,-4,-4);
  1111.     FrameRoundRect(&ItemBox,16,16);    
  1112.     PenSize(1,1);
  1113. }
  1114.  
  1115. //    Seed Random().
  1116. short 
  1117. Seed(void)
  1118. {
  1119.     unsigned long secs;
  1120.     
  1121.     GetDateTime(&secs);
  1122.     return( qd.randSeed = secs);
  1123.     
  1124. }
  1125.  
  1126. //    Returns positive random result between 0 and max-1 inclusive.
  1127. short 
  1128. Choose(short Max)
  1129. {
  1130.     long RawResult = labs( (long) Random() );
  1131.     return (short) ((RawResult*Max) / 32768);
  1132. }
  1133.  
  1134. // Alert user to Resource Manager Error.
  1135. void
  1136. ReportResError(char *Msg)
  1137. {
  1138.     OSErr    TheErr;
  1139.     char    Buf[256];
  1140.     
  1141.     if ( (TheErr = ResError()) != noErr) 
  1142.     {
  1143.         InitCursor();
  1144.         sprintf(Buf,"ResError %d: %s...Aborting.", TheErr, Msg);
  1145.         ArpAlert(Buf);
  1146.         Terminate();
  1147.     }
  1148. }